from dataclasses import dataclass
from enum import IntEnum
import struct
import time
from common import Crc16_Cal_Fun, show_hex


class E_HeadId(IntEnum):
    """
    协议头标识header
    """

    Client: int = 0xAA
    Client_V2: int = 0xAB
    Server: int = 0xBB
    Background_Service: int = 0x01


class E_CmdCode(IntEnum):
    """
    协议命令字
    """

    Login: int = 0x10
    ChargingCmd: int = 0x11
    ChargingConnectOK: int = 0x12
    FaultReport: int = 0x13
    StateReport: int = 0x14
    ControlCharge: int = 0x15
    Unlogin: int = 0x1F
    Unlogin_V2: int = 0xAF
    HeartBeat: int = 0xF0
    HeartBeat_V2: int = 0xA4
    ChargingConnectOK_V2: int = 0xA2


class E_ChargeState(IntEnum):
    """
    充电桩状态
    """

    Idle: int = 0x00
    Connect: int = 0x01  # 对接中,连接中
    Charging: int = 0x02
    Abnormal: int = 0x03
    Fault: int = 0xFF


class E_ChargingCmd(IntEnum):
    """
    充电命令
    """

    Connect: int = 0x01
    Charging: int = 0x02
    Stop: int = 0x03
    Disconnect: int = 0x04


@dataclass
class ChargingArgs:
    """
    充电参数:
        cmd_code 充电命令, 0x01:对接, 0x02:充电, 0x03:停止, 0x04:断开
        voltage 充电设备当前电压, 单位0.1v
        current 充电设备最大电流, 单位0.1A
        battery_type 充电设备电池类型, 0x01:磷酸铁锂电池, 0x02:三元锂电池, 0x03:铅酸电池
    """

    cmd: str = ""
    cmd_code: int = 0
    voltage: int = 0
    current: int = 0
    battery_type: int = 0
    station_id: int = 0

    def to_bytes(self) -> bytes:
        return struct.pack(
            "<BHHB",
            self.cmd_code,
            self.voltage,
            self.current,
            self.battery_type,
        )


@dataclass
class Frame:
    """
    充电桩tcp协议报文
        header 头标识 1B,uint8
        payload_len 报文长度 2B,uint16
        device_type 设备类型 2B,uint16
        client_id 客户端id 2B,uint16
        timestamp 时间戳 4B,uint32
        cmd_code 命令字 1B,uint8
        data 数据 nB
        crc16 crc校验码 2B,uint16
    """

    header: int = E_HeadId.Server
    payload_len: int = 0
    device_type: int = 0x03
    client_id: int = 0
    timestamp: int = 0
    cmd_code: int = 0
    data: bytes = b""
    crc16: int = 0

    def to_bytes(self) -> bytes:
        """
        组包: 封装成充电桩协议报文
        """
        self.timestamp = int(time.time())
        self.payload_len = 9 + len(self.data)
        data = struct.pack(
            f"<HHIB{len(self.data)}s",
            self.device_type,
            self.client_id,
            self.timestamp,
            self.cmd_code,
            self.data,
        )
        data = struct.pack("<H", self.payload_len) + data
        self.crc16 = Crc16_Cal_Fun(data)
        frame = struct.pack(
            f"<BHHHIB{len(self.data)}sH",
            self.header,
            self.payload_len,
            self.device_type,
            self.client_id,
            self.timestamp,
            self.cmd_code,
            self.data,
            self.crc16,
        )
        return frame

    def from_bytes(self, data: bytes):
        """
        解包: 解析充电桩协议报文
        """
        # 1.校验head
        if not data[0] in (E_HeadId.Client, E_HeadId.Client_V2):
            print(f"header校验失败, 丢弃 head:{data[0]:02x}")
            return None

        # 2.校验长度
        payload_len = struct.unpack_from("<H", data[1:3])[0]
        if payload_len != len(data) - 5:
            print("长度校验失败, 丢弃")
            return None
        data_len = payload_len - 9

        # 3.校验crc16
        cal_crc16 = Crc16_Cal_Fun(data[1:-2])
        data_crc16 = struct.unpack_from("<H", data[-2:])[0]
        if cal_crc16 != data_crc16:
            print("crc16校验失败, 丢弃")
            return None

        # 2.解析报文
        (
            self.header,
            self.payload_len,
            self.device_type,
            self.client_id,
            self.timestamp,
            self.cmd_code,
            self.data,
        ) = struct.unpack_from(f"<BHHHIB{data_len}s", data)
        return self


@dataclass
class ChargerClient:
    """
    充电桩客户端,保存状态与信息
    """

    socket: any = None
    device_type: int = 0
    client_id: int = 0
    password: int = 0
    hw_version: int = 0
    sw_version: int = 0
    product_data: int = 0
    # 充电状态
    charge_state: int = 0
    cur_current: int = 0
    max_current: int = 0
    cur_voltage: int = 0
    temp: int = 0
    battery_curve: int = 0
    motor_state: int = 0
    motor_comm_state: int = 0
    real_voltage: float = 0
    real_current: float = 0


if __name__ == "__main__":
    # 组包
    frame = Frame()
    frame.header = E_HeadId.Client
    frame.client_id = 0x2A
    frame.cmd_code = E_CmdCode.Login
    frame.data = b"1234"
    bytes_data = frame.to_bytes()
    show_hex(bytes_data, "bytes_data")

    # 解包
    recv_frame = Frame().from_bytes(bytes_data)
    print(
        f"id:{recv_frame.client_id:02X}, cmd:{recv_frame.cmd_code:02X}, data:{recv_frame.data}"
    )
